package com.houxm.jbapu.pool;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Vector;

public class ConnectionPool {
	
	private ConnectionParam param;
	private Vector<_Connection> connections = null;
	
	public ConnectionPool(ConnectionParam param) throws ClassNotFoundException, SQLException {
		this.param = param;
		initPool();
	}

	private void initPool() throws ClassNotFoundException, SQLException {
		connections = new Vector<_Connection>();
		Class.forName(param.getDriver());
		for (int i = 0; i < param.getInitialConnections(); i++) {
			Connection conn = DriverManager.getConnection(param.getUrl(), param.getUser(), param.getPassword());
			_Connection _conn = new _Connection(conn, false);
			connections.addElement(_conn);
		}
	}
	
	public synchronized Connection getConnection() throws SQLException {
		if(!param.isActive())
			return null;
		Connection conn = getOrNewConnection();
		int waitTimes = 1;
		while (conn == null) {
			if(waitTimes>param.getWaitTimes()) {
				break;
			}
			try {
				Thread.sleep(param.getEveryWait());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			waitTimes++;
			conn = getOrNewConnection();
		}
		return conn;
	}

	private Connection getOrNewConnection() {
		Connection conn = getFreeConnection();
		if (conn == null) {
			createConnections(param.getIncrementalConnections());
			conn = getFreeConnection();
		}
		return conn;
	}

	private void createConnections(int incrementalConnections) {
		// 循环创建指定数目的数据库连接
		for (int i = 0; i < incrementalConnections; i++) {
			// 是否连接池中的数据库连接的数量己经达到最大？最大值由类成员 maxConnections
			// 指出，如果 maxConnections 为 0 或负数，表示连接数量没有限制。
			// 如果连接数己经达到最大，即退出。
			if (param.getMaxConnections() > 0 && this.connections.size() >= param.getMaxConnections()) {
				break;
			}
			try {
				Connection conn = DriverManager.getConnection(param.getUrl(), param.getUser(), param.getPassword());
				_Connection _conn = new _Connection(conn, false);
				connections.addElement(_conn);
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	private Connection getFreeConnection() {
		Connection conn = null;
		for (Iterator<_Connection> iterator = connections.iterator(); iterator.hasNext();) {
			_Connection _Connection = iterator.next();
			if(!_Connection.isBusy()) {
				conn = _Connection.getConnection();
				_Connection.setBusy(true);
				break;
			}
		}
		return conn;
	}

	public void close() {
		if(connections==null) {
			return ;
		}
		this.param.setActive(false);
		_Connection c = null;
		for (Iterator<_Connection> iterator = connections.iterator(); iterator.hasNext();) {
			c = iterator.next();
			if(c.isBusy()) {
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			try {
				c.getConnection().close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			connections.remove(c);
		}
	}
	
	class _Connection implements InvocationHandler {
		
		private final static String CLOSE_METHOD_NAME = "close";
		private Connection conn = null;
		private boolean busy = false;
		private long lastAccessTime = System.currentTimeMillis();
		private Class<?>[] clazz = {Connection.class};
		
		_Connection(Connection conn, boolean busy) {
			this.conn = conn;
			this.busy = busy;
		}

		public Connection getConnection() {
			
			Connection conn2 = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), 
					clazz, this);
			return conn2;
		}

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			Object obj = null;
            // 判断是否调用了 close 的方法，如果调用 close 方法则把连接置为无用状态
            if(CLOSE_METHOD_NAME.equals(method.getName()))
                busy = false;
            else
                obj = method.invoke(conn, args);
            // 设置最后一次访问时间，以便及时清除超时的连接
            lastAccessTime = System.currentTimeMillis();
            return obj;
		}

		public void setBusy(boolean busy) {
			this.busy = busy;
		}
		
		public boolean isBusy() {
			return busy;
		}

		public long getLastAccessTime() {
			return lastAccessTime;
		}

		public void setLastAccessTime(long lastAccessTime) {
			this.lastAccessTime = lastAccessTime;
		}
	}

}
